package com.sl.proxy.server.service;

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Sets;
import com.sl.auth.core.util.StpCompositeUtil;
import com.sl.auth.core.util.StpLoginUserUtil;
import com.sl.proxy.server.base.page.PageInfo;
import com.sl.proxy.server.base.page.PageQuery;
import com.sl.proxy.server.constant.EnableStatusEnum;
import com.sl.proxy.server.constant.ExceptionConstant;
import com.sl.proxy.server.constant.OnlineStatusEnum;
import com.sl.proxy.server.controller.req.proxy.LicenseCreateReq;
import com.sl.proxy.server.controller.req.proxy.LicenseListReq;
import com.sl.proxy.server.controller.req.proxy.LicenseUpdateEnableStatusReq;
import com.sl.proxy.server.controller.req.proxy.LicenseUpdateReq;
import com.sl.proxy.server.controller.res.proxy.*;
import com.sl.proxy.server.dao.domain.entity.LicenseDO;
import com.sl.proxy.server.dao.domain.entity.PortMappingDO;
import com.sl.proxy.server.dao.mapper.LicenseMapper;
import com.sl.proxy.server.dao.mapper.PortMappingMapper;
import com.sl.proxy.server.service.bo.FlowLimitBO;
import com.sl.proxy.server.util.ParamCheckUtil;
import com.sl.proxy.server.util.ServiceException;
import com.sl.proxy.server.util.StringUtil;
import com.sl.sys.api.SysUserApi;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * license服务
 *
 * @author: aoshiguchen
 * @date: 2022/8/6
 */
@Component
@RequiredArgsConstructor
public class LicenseService {
    private final LicenseMapper licenseMapper;
    private final PortMappingMapper portMappingMapper;
    private final SysUserApi sysUserApi;
    private final VisitorChannelService visitorChannelService;
    // 流量限制缓存
    private final Cache<String, FlowLimitBO> flowLimitCache = CacheUtil.newLRUCache(200, 1000 * 60 * 5);

    @PreDestroy
    public void stop() {
        licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());

    }

    public PageInfo<LicenseListRes> page(PageQuery pageQuery, LicenseListReq req) {
        Page<LicenseDO> page = licenseMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<LicenseDO>().eq(req.getUserId() != null, LicenseDO::getUserId, req.getUserId()).eq(req.getIsOnline() != null, LicenseDO::getIsOnline, req.getIsOnline()).eq(req.getEnable() != null, LicenseDO::getEnable, req.getEnable()).orderByAsc(Arrays.asList(LicenseDO::getUserId, LicenseDO::getId)));
        List<LicenseListRes> respList = page.getRecords().stream().map(LicenseDO::toRes).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(page.getRecords())) {
            return PageInfo.of(respList, page);
        }
        if (!CollectionUtil.isEmpty(respList)) {
            List<String> userIds = respList.stream().map(LicenseListRes::getUserId).collect(Collectors.toList());
            List<JSONObject> userList = sysUserApi.getUserListByIdWithException(userIds);

            Map<String, JSONObject> userMap = userList.stream().collect(Collectors.toMap(v -> v.getStr("id"), Function.identity()));
            for (LicenseListRes item : respList) {
                JSONObject userDO = userMap.get(item.getUserId());
                if (null != userDO) {
                    item.setUserName(userDO.getStr("name"));
                }
                item.setLicenseKey(desensitization(item.getUserId(), item.getLicenseKey()));
            }
        }
        return PageInfo.of(respList, page);
    }

    public List<LicenseListRes> list(LicenseListReq req) {
        List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>().eq(null != req.getEnable(), LicenseDO::getEnable, req.getEnable()));
        List<LicenseListRes> licenseList = assembleConvertLicenses(list);
        return licenseList;
    }

    private List<LicenseListRes> assembleConvertLicenses(List<LicenseDO> list) {
        List<LicenseListRes> licenseList = list.stream().map(LicenseDO::toRes).collect(Collectors.toList());
        if (!CollectionUtil.isEmpty(licenseList)) {
            List<String> userIds = licenseList.stream().map(LicenseListRes::getUserId).collect(Collectors.toList());
            List<JSONObject> userList = sysUserApi.getUserListByIdWithException(userIds);
            Map<String, JSONObject> userMap = userList.stream().collect(Collectors.toMap(v -> v.getStr("id"), Function.identity()));
            for (LicenseListRes item : licenseList) {
                JSONObject userDO = userMap.get(item.getUserId());
                if (null != userDO) {
                    item.setUserName(userDO.getStr("name"));
                }
                item.setLicenseKey(desensitization(item.getUserId(), item.getLicenseKey()));
            }
        }
        return licenseList;
    }

    /**
     * 创建license
     *
     * @param req
     * @return
     */
    public LicenseCreateRes create(LicenseCreateReq req) {
        LicenseDO licenseDO = licenseMapper.checkRepeat(req.getUserId(), req.getName());
        ParamCheckUtil.checkExpression(null == licenseDO, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);

        String key = UUID.randomUUID().toString().replaceAll("-", "");
        Date now = new Date();

        licenseDO = new LicenseDO().setName(req.getName()).setLicenseKey(key).setUserId(req.getUserId()).setUpLimitRate(req.getUpLimitRate()).setDownLimitRate(req.getDownLimitRate()).setIsOnline(OnlineStatusEnum.OFFLINE.getStatus()).setEnable(EnableStatusEnum.ENABLE.getStatus());
        licenseDO.setCreateTime(now);
        licenseDO.setModifyTime(now);
        licenseMapper.insert(licenseDO);

        // 刷新流量限制缓存
        refreshFlowLimitCache(licenseDO.getId(), licenseDO.getUpLimitRate(), licenseDO.getDownLimitRate());
        return new LicenseCreateRes();
    }

    public LicenseUpdateRes update(LicenseUpdateReq req) {
        LicenseDO oldLicenseDO = licenseMapper.findById(req.getId());
        ParamCheckUtil.checkNotNull(oldLicenseDO, ExceptionConstant.LICENSE_NOT_EXIST);

        LicenseDO licenseCheck = licenseMapper.checkRepeat(oldLicenseDO.getUserId(), req.getName(), Sets.newHashSet(oldLicenseDO.getId()));
        ParamCheckUtil.checkMustNull(licenseCheck, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
        LambdaUpdateWrapper<LicenseDO> licenseDOLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        licenseDOLambdaUpdateWrapper.eq(LicenseDO::getId, req.getId()).set(LicenseDO::getName, req.getName()).set(LicenseDO::getUpLimitRate, req.getUpLimitRate()).set(LicenseDO::getDownLimitRate, req.getDownLimitRate());
        licenseDOLambdaUpdateWrapper.set(LicenseDO::getModifyTime, new Date());
        licenseMapper.update(null, licenseDOLambdaUpdateWrapper);

        // 刷新流量限制缓存
        refreshFlowLimitCache(req.getId(), req.getUpLimitRate(), req.getDownLimitRate());

        return new LicenseUpdateRes();
    }

    public LicenseDetailRes detail(String id) {
        LicenseDO licenseDO = licenseMapper.findById(id);
        if (null == licenseDO) {
            return null;
        }
        JSONObject userDO = sysUserApi.getUserByIdWithException(licenseDO.getUserId());
        String userName = "";
        if (null != userDO) {
            userName = userDO.getStr("name");
        }
        LicenseDetailRes licenseDetailRes = new LicenseDetailRes().setId(licenseDO.getId()).setName(licenseDO.getName()).setLicenseKey(desensitization(licenseDO.getUserId(), licenseDO.getLicenseKey())).setUserId(licenseDO.getUserId()).setUserName(userName).setIsOnline(licenseDO.getIsOnline()).setEnable(licenseDO.getEnable());
        licenseDetailRes.setCreateTime(licenseDO.getCreateTime());
        licenseDetailRes.setUpdateTime(licenseDO.getModifyTime());
        return licenseDetailRes;
    }

    /**
     * 更新license启用状态
     *
     * @param req
     * @return
     */
    public LicenseUpdateEnableStatusRes updateEnableStatus(LicenseUpdateEnableStatusReq req) {
        licenseMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
        // 更新VisitorChannel
        visitorChannelService.updateVisitorChannelByLicenseId(req.getId(), req.getEnable());
        return new LicenseUpdateEnableStatusRes();
    }

    /**
     * 删除license
     *
     * @param id
     */
    public void delete(String id) {
        List<PortMappingDO> portMappingDOList = portMappingMapper.findListByLicenseId(id);
        if (CollectionUtil.isNotEmpty(portMappingDOList)) {
            throw ServiceException.create(ExceptionConstant.LICENSE_CANNOT_BE_DELETED, portMappingDOList.size());
        }
        licenseMapper.deleteById(id);
        // 更新VisitorChannel
        visitorChannelService.updateVisitorChannelByLicenseId(id, EnableStatusEnum.DISABLE.getStatus());
        // 删除流量限制缓存
        flowLimitCache.remove(id);
    }

    /**
     * 重置license
     *
     * @param id
     */
    public void reset(String id) {
        String key = UUID.randomUUID().toString().replaceAll("-", "");
        Date now = new Date();

        licenseMapper.reset(id, key, now);
    }

    @InterceptorIgnore(tenantLine = "true")
    public LicenseDO findByKey(String license) {
        return licenseMapper.findByKey(license);
    }

    /**
     * 脱敏处理
     * 非当前登录人的license，一律脱敏
     *
     * @param userId
     * @param licenseKey
     * @return
     */
    private String desensitization(String userId, String licenseKey) {
        String currentUserId = StpCompositeUtil.getUserId();
        if (currentUserId.equals(userId)) {
            return licenseKey;
        }
        return licenseKey.substring(0, 10) + "****" + licenseKey.substring(licenseKey.length() - 10);
    }

    /**
     * 服务端项目停止、启动时，更新在线状态为离线
     */
    @PostConstruct
    public void init() {
        // 服务刚启动，所以默认所有license都是离线状态。解决服务突然关闭，在线状态来不及更新的问题
        licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
        // 刷新流量限制缓存
        List<LicenseDO> licenseDOList = licenseMapper.listAll();
        if (CollectionUtils.isEmpty(licenseDOList)) {
            for (LicenseDO licenseDO : licenseDOList) {
                refreshFlowLimitCache(licenseDO.getId(), licenseDO.getUpLimitRate(), licenseDO.getDownLimitRate());
            }
        }
    }


    /**
     * 刷新流量限制缓存
     *
     * @param id
     * @param upLimitRate
     * @param downLimitRate
     */
    private void refreshFlowLimitCache(String id, String upLimitRate, String downLimitRate) {
        if (null == id) {
            return;
        }
        flowLimitCache.put(id, new FlowLimitBO().setUpLimitRate(StringUtil.parseBytes(upLimitRate)).setDownLimitRate(StringUtil.parseBytes(downLimitRate)));
    }

    /**
     * 获取license的流量限制
     *
     * @param licenseId
     * @return
     */
    public FlowLimitBO getFlowLimit(String licenseId) {
        FlowLimitBO res = flowLimitCache.get(licenseId);
        if (null == res) {
            LicenseDO licenseDO = licenseMapper.queryById(licenseId);
            if (null != licenseDO) {
                refreshFlowLimitCache(licenseId, licenseDO.getUpLimitRate(), licenseDO.getDownLimitRate());
                res = flowLimitCache.get(licenseId);
            }
        }
        return res;
    }


    /**
     * 查询当前角色下的license，若为管理员 则返回全部license
     */
    public List<LicenseListRes> queryCurUserLicense(LicenseListReq req) {
        if (StpLoginUserUtil.isAdmin()) {
            return this.list(req);
        }

        List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>().eq(LicenseDO::getEnable, EnableStatusEnum.ENABLE.getStatus()).eq(LicenseDO::getUserId, StpCompositeUtil.getUserId()));
        List<LicenseListRes> licenseList = assembleConvertLicenses(list);
        return licenseList;
    }


}
